<?php
// +----------------------------------------------------------------------
// | User: DaMing
// +----------------------------------------------------------------------
// | Email: 3228985941@qq.com
// +----------------------------------------------------------------------
// | Date: 2022/3/24
// +----------------------------------------------------------------------
// | Time: 20:43
// +----------------------------------------------------------------------

namespace app\dm\traits;

use app\dm\exceptions\InvalidArgumentException;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use think\db\exception\ModelNotFoundException;
use think\exception\DbException;
use think\facade\Cache;
use think\Request;

/**
 * JwtToken处理
 * Trait JwtAuthTrait
 * @package app\http\traits
 */
trait JwtAuthTrait
{
    /**
     * @param bool $type 是否为换取新token
     * @param array $params
     * @return array
     */
    protected function createToken($model, $type = false,$params = [])
    {
        $time    = time();
        $pk      = $model->getPk();
        $exp     = $time + (int)config('jwt.exp');
        $payload = [
            'iss'   => config('jwt.iss'),
            'aud'   => config('jwt.aud'),
            'iat'   => config('jwt.iat'),
            'nbf'   => config('jwt.nbf'),
            'scope' => 'access',
            'exp'   => $exp,
            'data'  => array_merge(['uid' => $model->$pk], $params),
        ];
        $token   = JWT::encode($payload, config('jwt.app_secret'),config('jwt.alg'));
        // 设置token缓存
        $this->setTokenCache($token);
        // 组装数据
        $ret = [
            'token_type'   => 'Bearer',
            'token'        => $token,
            'expires_time' => date('Y-m-d H:i:s', $exp),
            'params'       => $payload['data']
        ];
        // 没有给类型则给个刷新token
        if (!$type) {
            $refresh_token = $this->createRefreshToken($model);
            $ret           = array_merge($ret, [
                'refresh_token'        => $refresh_token,
                'refresh_expires_time' => date('Y-m-d H:i:s', $time + (int)config('jwt.refresh_exp')),
            ]);
        }
        return $ret;
    }

    /**
     * 设置token的缓存
     * @param string $token
     */
    protected function setTokenCache($token)
    {
        Cache::set($token, $token, (int)config('jwt.exp'));
    }

    /**
     * 生成刷新Token
     * @param Admin $model
     * @param array $params
     * @return string
     */
    private function createRefreshToken($model, array $params = [])
    {
        $pk            = $model->getPk();
        $exp           = time() + (int)config('jwt.refresh_exp');
        $payload       = [
            'iss'   => config('jwt.iss'),
            'aud'   => config('jwt.aud'),
            'iat'   => config('jwt.iat'),
            'nbf'   => config('jwt.nbf'),
            'scope' => 'refresh',
            'exp'   => $exp,
            'data'  => array_merge(['uid' => $model->$pk], $params),
        ];
        $refresh_token = JWT::encode($payload, config('jwt.app_secret'),config('jwt.alg'));
        $this->setRefreshTokenCache($refresh_token);
        // 返回token
        return $refresh_token;
    }

    /**
     * 设置刷新缓存
     * @param string $refresh_token
     */
    protected function setRefreshTokenCache($refresh_token)
    {
        Cache::set($refresh_token, $refresh_token, (int)config('jwt.refresh_exp'));
    }

    /**
     * 解析Token
     * @param $jwt
     * @return array
     * @throws CustomDbException
     * @throws CustomExpireException
     * @throws CustomModelNotFoundException
     */
    protected function parseToken($jwt, $model)
    {
        $info = [];
        try {
            $payload = $this->getPayload($jwt);
            if (!empty($payload)) {
                $info = $model->where($model->getPk(), $payload->data->uid)->find();
            }
        } catch (ExpiredException $e) {
            throw new ExpireException();
        } catch (ModelNotFoundException $e) {
            throw new ModelNotFoundException();
        } catch (DbException $e) {
            throw new DbException();
        }
        return ['obj' => $info, 'payload' => $payload];
    }

    /**
     * 获取payload
     * @param $jwt
     * @return object|null
     */
    protected function getPayload($jwt)
    {
        JWT::$leeway = config('jwt.leeway');
        return JWT::decode($jwt, new Key(config('jwt.app_secret'), config('jwt.alg')));
    }

    /**
     * 判断缓存是否存在
     * @param string $token
     * @return bool
     */
    protected function checkTokenFromCache($token)
    {
        return Cache::has($token);
    }

    /**
     * 刷新token行为
     * @param string $old_token
     * @param string $token
     */
    protected function tokenRefreshLogic($old_token, $token)
    {
        $this->delTokenFromCache($old_token);
        $this->setTokenCache($token);
    }

    /**
     * 删除token的缓存
     * @param string $token
     */
    protected function delTokenFromCache($token)
    {
        Cache::rm($token);
    }

    /**
     * 获取token
     * @param Request $request
     * @return string
     * @throws CustomInvalidArgumentException
     */
    protected function getRequestToken(Request $request)
    {
        if (!$token = $this->getHeaderAuthorization($request)) {
            throw new InvalidArgumentException();
        }
        return trim(ltrim($token, 'Bearer'));
    }

    /**
     * 获取头部authorization
     * @param Request $request
     * @return string
     */
    protected function getHeaderAuthorization(Request $request)
    {
        return $request->header('authorization');
    }
}